package nl.dxn.tunnel.client.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.flush.FlushConsolidationHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.stream.ChunkedWriteHandler;
import nl.dxn.tunnel.CollectionUtils;
import nl.dxn.tunnel.client.config.IpPortProperties;
import nl.dxn.tunnel.client.config.TcpConfigProperties;
import nl.dxn.tunnel.client.handel.ProxyHttpHandler;
import nl.dxn.tunnel.client.handel.ProxyTcpHandler;
import nl.dxn.tunnel.codec.GzipCodec;
import nl.dxn.tunnel.codec.MessageCodec;
import nl.dxn.tunnel.enums.ProxyTypeEnum;
import nl.dxn.tunnel.enums.TransferMessageType;
import nl.dxn.tunnel.handel.IdleCheckHandler;
import nl.dxn.tunnel.po.HttpModeDto;
import nl.dxn.tunnel.po.MessageConfig;
import nl.dxn.tunnel.po.TransferMessage;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class AgentClient {

    private static final Logger logger = LoggerFactory.getLogger(AgentClient.class);

    private final Bootstrap bootstrap = new Bootstrap();
    private final NioEventLoopGroup clientWorkerGroup = new NioEventLoopGroup();
    Channel channel = null;
    private String ip;
    private int port;
    private String agentId;
    private String agentKey;
    private List<HttpModeDto> modes;

    private TcpConfigProperties properties;

    public AgentClient(String ip,int port,TcpConfigProperties properties, String agentId,String agentKey) throws IOException {
        this.ip=ip;
        this.port = port;
        this.properties = properties;
        this.agentId = agentId;
        this.agentKey = agentKey;

        if (properties.getProxyType() == ProxyTypeEnum.HTTP.code) {
            modes = new ArrayList<>();
            if(!CollectionUtils.isEmpty(properties.getServerTransferMap()))
            {

                for (Map.Entry<String, IpPortProperties> entry : properties.getServerTransferMap().entrySet()) {
                    HttpModeDto modeDto = new HttpModeDto();
                    modeDto.setName(entry.getKey());
                    modeDto.setRemoveMode(entry.getValue().isRemoveMode());
                    modes.add(modeDto);
                }
            }

            if(StringUtils.isNotBlank(properties.getDefaultIp()) && properties.getDefaultPort() >0 )
            {
                HttpModeDto modeDto = new HttpModeDto();
                modeDto.setName(properties.getServerId());
                modeDto.setRemoveMode(false);
                modes.add(modeDto);
            }
        }

        init();
        Runtime.getRuntime().addShutdownHook(new Thread(this::destroy));
    }

    private void init() throws IOException {
        //SslContext sslContext = buildSslContext();
        bootstrap.group(clientWorkerGroup)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
//                        ch.pipeline().addLast(sslContext.newHandler(ch.alloc()));
                        ch.pipeline().addLast(new GzipCodec());
                        //固定帧长解码器
                        ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                        ch.pipeline().addLast(new LengthFieldPrepender(4));
                        ch.pipeline().addLast(new ChunkedWriteHandler());

                        //自定义协议编解码器ServerApplication
                        ch.pipeline().addLast(new MessageCodec());
                        ch.pipeline().addLast(new IdleCheckHandler(60, 10, 0));
                        //服务器连接处理器
                        ch.pipeline().addLast(new FlushConsolidationHandler(5));
                        if (properties.getProxyType() == ProxyTypeEnum.TCP.code) {
                            ch.pipeline().addLast(new ProxyTcpHandler(properties));
                        } else {
                            ch.pipeline().addLast(new ProxyHttpHandler(properties));
                        }
                    }
                });
    }

    private SslContext buildSslContext() throws IOException {
        try (InputStream certChainFile = ClassLoader.getSystemResourceAsStream("client.crt"); InputStream keyFile = ClassLoader.getSystemResourceAsStream("pkcs8_client.key"); InputStream rootFile = ClassLoader.getSystemResourceAsStream("ca.crt")) {
            return SslContextBuilder.forClient().keyManager(certChainFile, keyFile).trustManager(rootFile).sslProvider(SslProvider.OPENSSL).build();
        }
    }

    public void destroy() {
        try {
            clientWorkerGroup.shutdownGracefully();
        } catch (Exception ex) {

        }
    }

    public boolean run() throws Exception {
        try {
//            if(clientWorkerGroup.isTerminated())
//            {
//                init(http, proxyType);
//            }
            if(channel != null && channel.isActive())
            {
                return true;
            }
            //启动服务器并绑定一个端口并且同步生成一个 ChannelFuture 对象
            ChannelFuture cf = bootstrap.connect(ip, port).sync();
            if (cf.isSuccess()) {
                channel = cf.channel();
                logger.info("agent客户端连接服务端成功,服务端地址IP:{},Http端口:{}", ip, port);
                //连接建立成功，发送注册请求
                TransferMessage message = new TransferMessage();
                message.setType(TransferMessageType.TYPE_REGISTER);
                message.setConfig(new MessageConfig());
                message.getConfig().setServerId(properties.getServerId());
                message.getConfig().setAgentId(agentId);
                message.getConfig().setAgentKey(agentKey);
                message.getConfig().setServerKey(properties.getServerKey());
                message.getConfig().setProxyType(properties.getProxyType());

                if(!CollectionUtils.isEmpty(modes))
                {
                    message.getConfig().setMode(modes);
                }

                cf.channel().writeAndFlush(message);
                return true;
            } else {
                logger.info("agent客户端连接服务端失败,服务端地址IP:{},Http端口:{},原因：{}", ip, port, cf.cause().getMessage());
                //this.destroy();
                return false;
            }
        }
        catch (Exception exception)
        {
            logger.error("agent客户端连接服务端失败,服务端地址IP:{},Http端口:{},原因：{}", ip, port, exception.getMessage());
            //this.destroy();
            return false;
        }
    }
}
